package nachos.threads;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import nachos.machine.*;


/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time.
 */


public class Alarm {
	
	//waiting thread class
	public class waitingThread implements Comparable<waitingThread> {
	
		//private member variables
		private KThread thread;
		private long waitTime;
		
		//constructor
		public waitingThread(KThread passedThread, long time){
			thread = passedThread;
			waitTime = time;
		}
		
		//compareTo
		public int compareTo(waitingThread passedThread)
		{
			return (new Long(waitTime)).compareTo(passedThread.getTime());
		}
		
		//mutators
		public void setThread(KThread passedThread){
			thread = passedThread;
		}
		public void setTime(long time){
			waitTime = time;
		}
		
		//accessors
		public KThread getThread(){
			return thread;
		}
		public long getTime(){
			return waitTime;
		}
	}
	
    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
	
    public Alarm() {
;
	Machine.timer().setInterruptHandler(new Runnable() {
		public void run() { timerInterrupt(); }
	    });
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread
     * that should be run.
     */
    public void timerInterrupt() 
    {
    
    	//get current time
    	long machineTime = Machine.timer().getTime();
    	
    	System.out.println("Timer Interrupt: " + machineTime);
    	
    	//check all waiting threads
    	//for (int i = 0; i < waitingThreads.size(); i++)
    	//{	
    	
    	;
    
    	while(waitingThreads.peek() !=null)
    	{
    		waitingThread thread = waitingThreads.peek();
    		if (thread.waitTime <=  Machine.timer().getTime())
    		{
    			
    			
    			waitingThreads.poll();
    			thread.getThread().ready();
    			System.out.println(thread.getThread().getName() + "'s waiting time is over.");
    			
    		}
    		else{
    			break;
    		}
    	}
    	//KThread.currentThread().yield();
	
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks,
     * waking it up in the timer interrupt handler. The thread must be
     * woken up (placed in the scheduler ready set) during the first timer
     * interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param	x	the minimum number of clock ticks to wait.
     *
     * @see	nachos.machine.Timer#getTime()
     */
    
    //PriorityQueue pQueue = new PriorityQueue();
    
    public void waitUntil(long x) 
    {	
    	
    	
    	boolean interrupt = Machine.interrupt().disable();
    	long wakeTime = Machine.timer().getTime() + x;
    	System.out.println(KThread.currentThread().getName() + " is waiting until " + wakeTime);
    	//create new thread object
    	
    	waitingThread newThread = new waitingThread(KThread.currentThread(), wakeTime);
    	
    	//add object to the linked list
    	waitingThreads.offer(newThread);
    	
    	
    	//sleep the current thread
    	KThread.currentThread().sleep();
    	
    	//restore interrupt
    	Machine.interrupt().restore(interrupt);
    }
    
    //linked list of waiting threads
    PriorityQueue <waitingThread> waitingThreads =new PriorityQueue<waitingThread>() ;
}
